a70f52
@@ -37,47 +37,37 @@
import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping;
 
 /**
- * Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
- * interface that maps handlers based on HTTP paths expressed through the
- * {@link RequestMapping} annotation at the type or method level.
+ * Implementation of the {@link org.springframework.web.servlet.HandlerMapping} interface that maps handlers based on
+ * HTTP paths expressed through the {@link RequestMapping} annotation at the type or method level.
  *
- * <p>Registered by default in {@link org.springframework.web.servlet.DispatcherServlet}
- * on Java 5+. <b>NOTE:</b> If you define custom HandlerMapping beans in your
- * DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean
- * explicitly, since custom HandlerMapping beans replace the default mapping strategies.
- * Defining a DefaultAnnotationHandlerMapping also allows for registering custom
- * interceptors:
+ * <p>Registered by default in {@link org.springframework.web.servlet.DispatcherServlet} on Java 5+. <b>NOTE:</b> If you
+ * define custom HandlerMapping beans in your DispatcherServlet context, you need to add a
+ * DefaultAnnotationHandlerMapping bean explicitly, since custom HandlerMapping beans replace the default mapping
+ * strategies. Defining a DefaultAnnotationHandlerMapping also allows for registering custom interceptors:
  *
- * <pre class="code">
- * &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
- *   &lt;property name="interceptors"&gt;
- *     ...
- *   &lt;/property&gt;
- * &lt;/bean&gt;</pre>
+ * <pre class="code"> &lt;bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"&gt;
+ * &lt;property name="interceptors"&gt; ... &lt;/property&gt; &lt;/bean&gt;</pre>
  *
- * Annotated controllers are usually marked with the {@link Controller} stereotype
- * at the type level. This is not strictly necessary when {@link RequestMapping} is
- * applied at the type level (since such a handler usually implements the
- * {@link org.springframework.web.servlet.mvc.Controller} interface). However,
- * {@link Controller} is required for detecting {@link RequestMapping} annotations
- * at the method level if {@link RequestMapping} is not present at the type level.
+ * Annotated controllers are usually marked with the {@link Controller} stereotype at the type level. This is not
+ * strictly necessary when {@link RequestMapping} is applied at the type level (since such a handler usually implements
+ * the {@link org.springframework.web.servlet.mvc.Controller} interface). However, {@link Controller} is required for
+ * detecting {@link RequestMapping} annotations at the method level if {@link RequestMapping} is not present at the type
+ * level.
  *
- * <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping
- * expressed at the class level (if any). HTTP paths need to uniquely map onto
- * specific handler beans, with any given HTTP path only allowed to be mapped
- * onto one specific handler bean (not spread across multiple handler beans).
- * It is strongly recommended to co-locate related handler methods into the same bean.
+ * <p><b>NOTE:</b> Method-level mappings are only allowed to narrow the mapping expressed at the class level (if any).
+ * HTTP paths need to uniquely map onto specific handler beans, with any given HTTP path only allowed to be mapped onto
+ * one specific handler bean (not spread across multiple handler beans). It is strongly recommended to co-locate related
+ * handler methods into the same bean.
  *
- * <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing
- * annotated handler methods, as mapped by this HandlerMapping. For
- * {@link RequestMapping} at the type level, specific HandlerAdapters such as
- * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
+ * <p>The {@link AnnotationMethodHandlerAdapter} is responsible for processing annotated handler methods, as mapped by
+ * this HandlerMapping. For {@link RequestMapping} at the type level, specific HandlerAdapters such as {@link
+ * org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
  *
  * @author Juergen Hoeller
  * @author Arjen Poutsma
- * @since 2.5
  * @see RequestMapping
  * @see AnnotationMethodHandlerAdapter
+ * @since 2.5
  */
 public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {
 
@@ -85,23 +75,19 @@
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
 
 	private final Map<Class, RequestMapping> cachedMappings = new HashMap<Class, RequestMapping>();
 
-
 	/**
-	 * Set whether to register paths using the default suffix pattern as well:
-	 * i.e. whether "/users" should be registered as "/users.*" and "/users/" too.
-	 * <p>Default is "true". Turn this convention off if you intend to interpret
-	 * your <code>@RequestMapping</code> paths strictly.
-	 * <p>Note that paths which include a ".xxx" suffix or end with "/" already will not be
-	 * transformed using the default suffix pattern in any case.
+	 * Set whether to register paths using the default suffix pattern as well: i.e. whether "/users" should be registered
+	 * as "/users.*" and "/users/" too. <p>Default is "true". Turn this convention off if you intend to interpret your
+	 * <code>@RequestMapping</code> paths strictly. <p>Note that paths which include a ".xxx" suffix or end with "/"
+	 * already will not be transformed using the default suffix pattern in any case.
 	 */
 	public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) {
 		this.useDefaultSuffixPattern = useDefaultSuffixPattern;
 	}
 
-
 	/**
-	 * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping}
-	 * annotation on the handler class and on any of its methods.
+	 * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping} annotation on the handler
+	 * class and on any of its methods.
 	 */
 	@Override
 	protected String[] determineUrlsForHandler(String beanName) {
@@ -120,11 +106,19 @@
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
 					if (!typeLevelPattern.startsWith("/")) {
 						typeLevelPattern = "/" + typeLevelPattern;
 					}
+					boolean hasEmptyMethodLevelMappings = false;
 					for (String methodLevelPattern : methodLevelPatterns) {
-						String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
-						addUrlsForPath(urls, combinedPattern);
+						if (methodLevelPattern == null) {
+							hasEmptyMethodLevelMappings = true;
+						} else {
+							String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
+							addUrlsForPath(urls, combinedPattern);
+						}
+					}
+					if (hasEmptyMethodLevelMappings ||
+							org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {
+						addUrlsForPath(urls, typeLevelPattern);
 					}
-					addUrlsForPath(urls, typeLevelPattern);
 				}
 				return StringUtils.toStringArray(urls);
 			}
@@ -144,6 +138,9 @@
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
 
 	/**
 	 * Derive URL mappings from the handler's method-level mappings.
+	 *
+	 * <p>The returned array may contain {@code null}, indicating an empty {@link RequestMapping} value.
+	 *
 	 * @param handlerType the handler type to introspect
 	 * @return the array of mapped URLs
 	 */
@@ -156,19 +153,25 @@
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
 				public void doWith(Method method) {
 					RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
 					if (mapping != null) {
-						String[] mappedPaths = mapping.value();
-						for (String mappedPath : mappedPaths) {
-							addUrlsForPath(urls, mappedPath);
+						String[] mappedPatterns = mapping.value();
+						if (mappedPatterns.length > 0) {
+							for (String mappedPattern : mappedPatterns) {
+								addUrlsForPath(urls, mappedPattern);
+							}
+						} else {
+							// empty method-level RequestMapping
+							urls.add(null);
 						}
 					}
 				}
-			});
+			}, ReflectionUtils.NON_BRIDGED_METHODS);
 		}
 		return StringUtils.toStringArray(urls);
 	}
 
 	/**
 	 * Add URLs and/or URL patterns for the given path.
+	 *
 	 * @param urls the Set of URLs for the current bean
 	 * @param path the currently introspected path
 	 */
@@ -180,9 +183,9 @@
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
 		}
 	}
 
-
 	/**
 	 * Validate the given annotated handler against the current request.
+	 *
 	 * @see #validateMapping
 	 */
 	@Override
@@ -197,8 +200,9 @@
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
 	}
 
 	/**
-	 * Validate the given type-level mapping metadata against the current request,
-	 * checking HTTP request method and parameter conditions.
+	 * Validate the given type-level mapping metadata against the current request, checking HTTP request method and
+	 * parameter conditions.
+	 *
 	 * @param mapping the mapping metadata to validate
 	 * @param request current HTTP request
 	 * @throws Exception if validation failed
@@ -220,9 +224,9 @@
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
 
 		String[] mappedHeaders = mapping.headers();
 		if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
-			throw new ServletRequestBindingException("Header conditions \"" +
-					StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
-					"\" not met for actual request");
+			throw new ServletRequestBindingException(
+					"Header conditions \"" + StringUtils.arrayToDelimitedString(mappedHeaders, ", ") +
+							"\" not met for actual request");
 		}
 	}
 
